/*  -*- mode: c++; coding: utf-8; c-file-style: "stroustrup"; -*-

    Copyright 2008 Asier Aguirre <asier.aguirre@gmail.com>
    This file is part of Intromit.

    Intromit is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Intromit is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Intromit.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "render_obj_camera.hh"
#include "render_obj_frame.hh"
#include "render_params.hh"
#include "render_attr.hh"
#include "render_util.hh"
#include "event_mng.hh"
#include "ogl.hh"
#include "key_names.hh"

#include <math.h>

// DEFINES
#define CAM_AZIM_RATIO         (0.2*M_PI/180)
#define CAM_ELEV_RATIO         (0.2*M_PI/180)
#define CAM_BANK_RATIO         (2e-4*M_PI/180)
#define CAM_AZIM_MOMENTUM      1e-5
#define CAM_ELEV_MOMENTUM      1e-5
#define CAM_BANK_MOMENTUM      1e-5
#define CAM_AZIM_MAX_DIFF      (0.02*M_PI/180)
#define CAM_ELEV_MAX_DIFF      (0.02*M_PI/180)
#define CAM_BANK_MAX_DIFF      (0.02*M_PI/180)
#define CAM_POS_MAX_DIFF       0.002
#define CAM_MOVEMENT_VELO      1e-4
#define CAM_MOVEMENT_INERTIA   1e-5
#define CAM_BANK_VECTOR        3
#define CAM_POS_VELO_ATT_1     0.999
#define CAM_POS_VELO_ATT_2     0.995
#define CAM_ATTRACTION_POS_R   1e-5  // attraction ratio for position
#define CAM_ATTRACTION_ANG_R   1e-6  // attraction ratio for angle
#define CAM_HOR_COLOR          0.1, 0.0, 0.0, 0.2

CLASS_INIT(render_obj_camera); // RENDER_OBJ_CAMERA CLASS

render_obj_camera::render_obj_camera()
{
    CLASS_CTOR(render_obj_camera);
    ctor();
}

render_obj_camera::~render_obj_camera()
{
    CLASS_DTOR(render_obj_camera);
    dtor();
}

void render_obj_camera::ctor()
{
    // default camera parameters
    fov=70;   // degrees
    zmin=0.5;
    zmax=100;
    aspect=1.0;

    for(int i=0; i<IS_SPHERIC; i++) angle[i]=angle_goto[i]=0.0;
    for(int i=0; i<IS_3D; i++) position[i]=position_goto[i]=position_bak[i]=0.0;

    // default movement parameters
    angle_mouse_ratio[IS_AZIM]=CAM_AZIM_RATIO;
    angle_mouse_ratio[IS_ELEV]=CAM_ELEV_RATIO;
    angle_mouse_ratio[IS_BANK]=CAM_BANK_RATIO;
    angle_momentum[IS_AZIM]=CAM_AZIM_MOMENTUM;
    angle_momentum[IS_ELEV]=CAM_ELEV_MOMENTUM;
    angle_momentum[IS_BANK]=CAM_BANK_MOMENTUM;
    movement_velo=CAM_MOVEMENT_VELO;
    movement_inertia=CAM_MOVEMENT_INERTIA;
    update_timer=event_mng::time_now();
    velo_att[0]=CAM_POS_VELO_ATT_1;
    velo_att[1]=CAM_POS_VELO_ATT_2;

    // attraction reset
    reset_attraction();

    // horizon
    horizon=true;
    n_horizon_parallels=7;
    n_horizon_parpoints=64;
    n_horizon_meridians=8;
    n_horizon_merpoints=4;
    horizon_call_list=0;
    render_color_anim::init_color(horizon_color, CAM_HOR_COLOR);
}

void render_obj_camera::dtor()
{
    // horizon
    if(horizon_call_list)
    {
	glDeleteLists(horizon_call_list, 1);
	horizon_call_list=0;
    }
}

bool render_obj_camera::init(render_mng *ctx)
{
    // need to be here (after ctor) to recalculate focal
    set_camera_parameters(fov, zmin, zmax, aspect);

    // actions
    struct
    {
	unsigned act_type, act_seq, act_coord, act_cursor, act_key;
	unsigned key_code, key_status;
	unsigned obj_id, obj_status;
	unsigned selector;
	const char *tip;
    } act[]=
      {
	  {
	      EM_ACTION_TYPE_INDEX_0, EM_ACTION_SEQ_TWICE, EM_ACTION_COORD_INC_INF, EM_ACTION_CURSOR_CENTER, EM_ACTION_KEY_NO_REP,
	      EM_BUTKEY_OFF | IS_BUTTON_LEFT, 0,
	      IS_TYPE_CAMERA, IS_STATUS_ANY, IS_SEL_NONE,
	      "Camera: Navigate"
	  },
	  {
	      EM_ACTION_TYPE_INDEX_0, EM_ACTION_SEQ_CANCEL, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      EM_BUTKEY_OFF | IS_BUTTON_RIGHT, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Navigate cancel"
	  },
	  {
	      EM_ACTION_TYPE_INT_V0P, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_D, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Strafe right"
	  },
	  {
	      EM_ACTION_TYPE_INT_V0N, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_A, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Strafe left"
	  },
	  {
	      EM_ACTION_TYPE_INT_V1P, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_R, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Strafe up"
	  },
	  {
	      EM_ACTION_TYPE_INT_V1N, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_F, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Strafe down"
	  },
	  {
	      EM_ACTION_TYPE_INT_V2P, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_S, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Back"
	  },
	  {
	      EM_ACTION_TYPE_INT_V2N, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_W, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Advance"
	  },
	  {
	      EM_ACTION_TYPE_INT_V3P, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_E, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Bank right"
	  },
	  {
	      EM_ACTION_TYPE_INT_V3N, EM_ACTION_SEQ_ONCE, EM_ACTION_COORD_NONE, EM_ACTION_CURSOR_NORMAL, EM_ACTION_KEY_NORMAL,
	      IS_KN_Q, 0,
	      IS_TYPE_CAMERA, IS_STATUS_FOCUS_AC(EM_ACTION_TYPE_INDEX_0), IS_SEL_NONE,
	      "Camera: Bank left"
	  },
	  {
	      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL
	  }
      };

    for(int i=0; act[i].tip; i++)
	ctx->get_event()->action_register(
	    EM_SET_ACTION(act[i].act_type, act[i].act_seq, act[i].act_coord, act[i].act_cursor, act[i].act_key),
	    act[i].key_code, act[i].key_status, act[i].obj_id, act[i].obj_status, act[i].selector, act[i].tip);

    return render_obj::init(ctx);
}

void render_obj_camera::render(render_mng *ctx)
{
    // check if further updates are required
    bool update=false;

    // update modelview matrix before setting it
    if(ctx->get_pass()==RENDER_PASS_PRECALC)
    {
	// cinematics
	update_cinematics(ctx);

	// reset attraction
	reset_attraction();

	// angle analysis
	if(fabs(angle_goto[IS_AZIM]-angle[IS_AZIM])>CAM_AZIM_MAX_DIFF ||
	   fabs(angle_goto[IS_ELEV]-angle[IS_ELEV])>CAM_ELEV_MAX_DIFF ||
	   fabs(angle_goto[IS_BANK]-angle[IS_BANK])>CAM_BANK_MAX_DIFF)
	{
	    matrix_angle_update();
	    update=true;
	}
	else
	{
	    // position analysis
	    event_mng *e=ctx->get_event();
	    if(e->action_vector_active() ||
	       fabs(position[IS_X]-position_goto[IS_X])>CAM_POS_MAX_DIFF ||
	       fabs(position[IS_Y]-position_goto[IS_Y])>CAM_POS_MAX_DIFF ||
	       fabs(position[IS_Z]-position_goto[IS_Z])>CAM_POS_MAX_DIFF)
		update=true;
	    else
	    {
		// equalize
		for(int i=0; i<IS_SPHERIC; i++)
		{
		    if(angle[i]!=angle_goto[i])
		    {
			angle[i]=angle_goto[i];
			update=true;
		    }
		}
		if(update) matrix_angle_update();
		for(int i=0; i<IS_3D; i++)
		{
		    if(position[i]!=position_goto[i])
		    {
			position[i]=position_goto[i];
			update=true;
		    }
		}
	    }
	}

	// further updates
	if(update)
	{
	    // further updates will be required
	    matrix_position_update();
	}
    }

    // check perspective matrix update
    if(aspect!=ctx->get_obj_frame()->get_aspect())
	set_camera_parameters(fov, zmin, zmax, ctx->get_obj_frame()->get_aspect());

    render_init(ctx);

    if(ctx->get_pass()==RENDER_PASS_PRECALC)
    {
	// further updates
	if(update)
	{
	    // further updates will be required
	    forced_pass=RENDER_PASS_PRECALC;
	    required_pass=1<<RENDER_PASS_PRECALC | 1<<RENDER_PASS_SELECT;
	}
	else
	    // required passes for camera
	    required_pass=1<<RENDER_PASS_SELECT;

	if(horizon) required_pass|=1<<RENDER_PASS_OPAQUE;
    }
    else if(ctx->get_pass()==RENDER_PASS_SELECT)
    {
	// select always
	select_always(1.0);
    }
    else if(ctx->get_pass()==RENDER_PASS_OPAQUE)
	render_horizon();

    // save this camera in context
    render_obj_camera *old_camera=ctx->get_obj_camera();
    ctx->set_obj_camera(this);

    render_children(ctx);

    // restore old camera
    ctx->set_obj_camera(old_camera);

    render_finish(ctx);
}

bool render_obj_camera::event(render_mng *ctx)
{
    render_obj::event(ctx);
    if(ctx->get_event_last())
    {
	// status event: IS_STATUS_*
	// to remove and add cursor when focus entering/exiting
	if(ctx->get_event_last() & IS_STATUS_FOCUS) ctx->set_forced_pass(RENDER_PASS_HUD, this);
    }
    else
    {
	event_mng *e=ctx->get_event();
	unsigned action;
	if((action=e->get_action_last()))
	    return action_dispatcher(ctx, EM_GET_ACTION_TYPE(action), EM_GET_ACTION_EVENT(action));
    }
    return false; // not managed
}

unsigned render_obj_camera::get_type()
{
    return IS_TYPE_CAMERA;
}

bool render_obj_camera::action_dispatcher(render_mng *ctx, unsigned type, unsigned event)
{
    switch(type)
    {
	case EM_ACTION_TYPE_INDEX_0: // navigate
	    return action_navigate(ctx, event);
	case EM_ACTION_TYPE_CENTER: // center
	    return action_center(ctx);
	case EM_ACTION_TYPE_OBJ_CAMERA_ATTRACTOR: // attraction field
	    return action_attraction(ctx);
	default:
	    if(event==EM_ACTION_EVENT_VECTOR)
		return action_navigate(ctx, event);
	    break;
    }
    return true;
}

bool render_obj_camera::action_navigate(render_mng *ctx, unsigned event)
{
    switch(event)
    {
    case EM_ACTION_EVENT_MOVE:
	{
	    // cinematics
	    update_cinematics(ctx);
	    angle_goto[IS_AZIM]+=ctx->get_focus_coord(IS_X)*angle_mouse_ratio[IS_AZIM];
	    angle_goto[IS_ELEV]-=ctx->get_focus_coord(IS_Y)*angle_mouse_ratio[IS_ELEV];
	    // limit elevation
	    if(angle_goto[IS_ELEV]>0.5*M_PI) angle_goto[IS_ELEV]=0.5*M_PI;
	    else if(angle_goto[IS_ELEV]<-0.5*M_PI) angle_goto[IS_ELEV]=-0.5*M_PI;
	    // force render if far from goto angle
	    if(fabs(angle_goto[IS_AZIM]-angle[IS_AZIM])>CAM_AZIM_MAX_DIFF ||
	       fabs(angle_goto[IS_ELEV]-angle[IS_ELEV])>CAM_ELEV_MAX_DIFF)
		ctx->set_forced_pass(RENDER_PASS_PRECALC, this);
	}
	break;
    case EM_ACTION_EVENT_INIT:
	// unwrap coordinates to avoid precision issues
	for(int i=0; i<IS_SPHERIC; i++)
	{
	    angle[i]=fmod(angle[i], 2*M_PI);
	    angle_bak[i]=angle_goto[i]=fmod(angle_goto[i], 2*M_PI);
	}
	for(int i=0; i<IS_3D; i++)
	    position_bak[i]=position_goto[i];
	break;
    case EM_ACTION_EVENT_FINISH:
	// nothing to do here
	break;
    case EM_ACTION_EVENT_CANCEL:
	// go back if necesary
	if(fabs(angle_bak[IS_AZIM]-angle[IS_AZIM])>CAM_AZIM_MAX_DIFF ||
	   fabs(angle_bak[IS_ELEV]-angle[IS_ELEV])>CAM_ELEV_MAX_DIFF ||
	   fabs(angle_bak[IS_BANK]-angle[IS_BANK])>CAM_BANK_MAX_DIFF ||
	   fabs(position_bak[IS_X]-position[IS_X])>CAM_POS_MAX_DIFF ||
	   fabs(position_bak[IS_Y]-position[IS_Y])>CAM_POS_MAX_DIFF ||
	   fabs(position_bak[IS_Z]-position[IS_Z])>CAM_POS_MAX_DIFF)
	{
	    for(int i=0; i<IS_SPHERIC; i++) angle_goto[i]=angle_bak[i];
	    for(int i=0; i<IS_3D; i++) position_goto[i]=position_bak[i];
	    angle_unwrap_nearest();
	    ctx->set_forced_pass(RENDER_PASS_PRECALC, this);

	    // stop inertia (velociry <= 0)
	    ctx->get_event()->action_vector_resetf();
	}
	break;
    case EM_ACTION_EVENT_VECTOR:
	// force precalc pass
	ctx->set_forced_pass(RENDER_PASS_PRECALC, this);
    }
    return true;
}

bool render_obj_camera::action_center(render_mng *ctx)
{
    double *c_render=ctx->calculate_selection_modelview_nocam_matrix();

    // separate from camera and set in global coordinates
    double desp=25/**/;
    c_render[12]+=desp*c_render[8];
    c_render[13]+=desp*c_render[9];
    c_render[14]+=desp*c_render[10];

    goto_position(ctx, &c_render[12], &c_render[8], &c_render[4]);
    return true;
}

bool render_obj_camera::action_attraction(render_mng *ctx)
{
    action_attraction_str *attr=(action_attraction_str *)ctx->get_action_params();

    // only one component from one attractor
    if(n_attraction<=0 || attraction.velocity<attr->velocity)
    {
	for(int i=0; i<IS_3D; i++)
	{
	    attraction.position[i]= attr->position[i];
	    attraction.lookat[i]=   attr->lookat[i];
	    attraction.vertical[i]= attr->vertical[i];
	    attraction.velocity=    attr->velocity;
	}
	n_attraction=1;
    }
    return true;
}

void render_obj_camera::goto_position(render_mng *ctx, double pos[IS_3D], double lookat[IS_3D], double vertical[IS_3D])
{
    // cinematics
    update_cinematics(ctx);

    // stop inertia (velocity <= 0)
    ctx->get_event()->action_vector_resetf();

    // update goto position
    for(int i=0; i<IS_3D; i++) position_goto[i]=pos[i];

    // update angles
    angles_from_vectors(lookat, vertical, angle_goto);

    // redraw
    angle_unwrap_nearest();
    ctx->set_forced_pass(RENDER_PASS_PRECALC, this);
}

void render_obj_camera::angle_unwrap_nearest()
{
    for(int i=0; i<IS_SPHERIC; i++)
    {
	while(angle[i]-angle_goto[i]>M_PI) angle[i]-=2*M_PI;
	while(angle_goto[i]-angle[i]>M_PI) angle[i]+=2*M_PI;
    }
}

void render_obj_camera::update_cinematics(render_mng *ctx)
{
    // first, make calculations that depends on current camera modelview
    calculate_attraction();

    // time from last update
    double time_diff=(double)update_time();

    // second update movement towards goto position with local time

    // update angle
    update_angle(time_diff);

    // update vectors
    update_vector(ctx, time_diff);

    // then update goto position

    // keyboard vectors goto
    update_vector_goto(ctx);

    // attraction goto
    update_attraction(time_diff);
}

int render_obj_camera::update_time()
{
    int now=event_mng::time_now();
    int time_diff=now-update_timer;
    update_timer=now;
    return time_diff;
}

void render_obj_camera::update_vector_goto(render_mng *ctx)
{
    // only update vectors if action was originted by this object
    if(ctx->get_focus_obj()!=this) return;

    event_mng *e=ctx->get_event();
    //e->action_vector_update();
    float incs[EM_ACTION_N_VECTOR];
    if(e->action_vector_active())
	e->action_vector_updatef(incs, velo_att[0]);
    else
	e->action_vector_updatef(incs, velo_att[1]);

    // move goto position
    double *c=modelview.get_coefs();
    for(int i=0; i<IS_3D; i++)
	for(int j=0; j<IS_3D; j++)
	    position_goto[j]+=c[i+(j<<2)]*incs[i]*movement_velo;

    // banking is extracted from vector, not from mouse movement
    if(e->action_vector_reset_if_opposite(CAM_BANK_VECTOR))
    {
	angle_goto[IS_BANK]=0.0;
	angle_unwrap_nearest();
	e->action_vector_resetf(CAM_BANK_VECTOR);
    }
    else angle_goto[IS_BANK]+=(double)incs[CAM_BANK_VECTOR]*CAM_BANK_RATIO;

    //e->action_vector_reset();
}

void render_obj_camera::update_vector(render_mng *ctx, double time_diff)
{
    // move position to goto position
    for(int i=0; i<IS_3D; i++)
	position[i]+=(position_goto[i]-position[i])*movement_inertia*time_diff/(1.0+movement_inertia*time_diff);
}

void render_obj_camera::update_angle(double time_diff)
{
    // update current angles
    for(int i=0; i<IS_SPHERIC; i++)
	angle[i]+=(angle_goto[i]-angle[i])*angle_momentum[i]*time_diff/(1.0+angle_momentum[i]*time_diff);
}

void render_obj_camera::reset_attraction()
{
    for(int i=0; i<IS_3D; i++)
	attraction.position[i]=attraction.lookat[i]=attraction.vertical[i]=0.0;
    attraction.velocity=0;
    n_attraction=0;
}

void render_obj_camera::calculate_attraction()
{
    if(n_attraction>0)
    {
	// promediate vectors
	if(n_attraction!=1)
	{
	    double inv_n=1.0/(double)n_attraction;
	    for(int i=0; i<IS_3D; i++)
	    {
		attraction.position[i]*=inv_n;
		attraction.lookat[i]*=inv_n;
		attraction.vertical[i]*=inv_n;
	    }
	}
	n_attraction=-1;

	// calculate final angles and position
	for(int i=0; i<IS_3D; i++) attraction.calculated_position[i]=attraction.position[i];
	angles_from_vectors(attraction.lookat, attraction.vertical, attraction.calculated_angle);
    }
}

void render_obj_camera::update_attraction(double time_diff)
{
    if(n_attraction<0)
    {
	// velocity of the movement
	double velo=time_diff*attraction.velocity;
	double v[IS_3D], dist, velo2;

	// update position with time diff
	for(int i=0; i<IS_3D; i++) v[i]=attraction.calculated_position[i]-position_goto[i];
	if((dist=RO_MODULE(v))>CAM_POS_MAX_DIFF)
	{
	    velo2=velo*CAM_ATTRACTION_POS_R;
	    if(velo2>=dist)
		for(int i=0; i<IS_3D; i++) position_goto[i]=attraction.calculated_position[i];
	    else
	    {
		velo2/=dist;
		for(int i=0; i<IS_3D; i++) position_goto[i]+=v[i]*velo2;
	    }
	}

	// update angles
	for(int i=0; i<IS_SPHERIC; i++)
	{
	    v[i]=attraction.calculated_angle[i]-angle_goto[i];
	    while(v[i]>M_PI) v[i]-=2*M_PI;
	    while(v[i]<-M_PI) v[i]+=2*M_PI;
	}
	if((dist=RO_MODULE(v))>CAM_AZIM_MAX_DIFF)
	{
	    velo2=velo*CAM_ATTRACTION_ANG_R;
	    if(velo2>=dist)
	    {
		for(int i=0; i<IS_SPHERIC; i++) angle_goto[i]=attraction.calculated_angle[i];
		angle_unwrap_nearest();
	    }
	    else
	    {
		velo2/=dist;
		for(int i=0; i<IS_SPHERIC; i++) angle_goto[i]+=v[i]*velo2;
	    }
	}
    }
}

void render_obj_camera::matrix_position_update()
{
    double *c=modelview.get_coefs();
    c[12]=-(c[0]*position[0]+c[4]*position[1]+c[8]* position[2]);
    c[13]=-(c[1]*position[0]+c[5]*position[1]+c[9]* position[2]);
    c[14]=-(c[2]*position[0]+c[6]*position[1]+c[10]*position[2]);
}

void render_obj_camera::matrix_angle_update()
{
    double *c=modelview.get_coefs();
    matrix_precalc_trig();
    matrix_get_vertical_vector(c+1);
    matrix_get_traversal_vector(c+0);
    matrix_get_longitudinal_vector(c+2);

    // bank
    if(fabs(angle[IS_BANK])>CAM_BANK_MAX_DIFF)
    {
	double v[3]={c[2], c[6], c[10]};
	modelview.do_rotation(angle[IS_BANK], v);
    }
}

void render_obj_camera::matrix_precalc_trig()
{
    prec_c_azim=cos(angle[IS_AZIM]); prec_s_azim=sin(angle[IS_AZIM]);
    prec_c_elev=cos(angle[IS_ELEV]); prec_s_elev=sin(angle[IS_ELEV]);
}

void render_obj_camera::matrix_precalc_trig(double azim, double elev)
{
    prec_c_azim=cos(azim); prec_s_azim=sin(azim);
    prec_c_elev=cos(elev); prec_s_elev=sin(elev);
}

void render_obj_camera::matrix_get_longitudinal_vector(double *v)
{
    v[0]=prec_c_azim*prec_c_elev;
    v[4]=prec_s_elev;
    v[8]=prec_s_azim*prec_c_elev;
}

void render_obj_camera::matrix_get_traversal_vector(double *v)
{
    v[0]=prec_s_azim;
    v[4]=0;
    v[8]=-prec_c_azim;
}

void render_obj_camera::matrix_get_vertical_vector(double *v)
{
    v[0]=-prec_s_elev*prec_c_azim;
    v[4]=prec_c_elev;
    v[8]=-prec_s_elev*prec_s_azim;
}

void render_obj_camera::render_horizon()
{
    glPushMatrix();
    glTranslated(position[0], position[1], position[2]);

    if(!horizon_call_list)
    {
	horizon_call_list=glGenLists(1);
	glNewList(horizon_call_list, GL_COMPILE_AND_EXECUTE);

	// render to list and display
	glPushAttrib(GL_DEPTH_BUFFER_BIT | GL_LIGHTING_BIT | GL_COLOR_BUFFER_BIT);
	glDepthMask(GL_FALSE);
	glDisable(GL_LIGHTING);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4fv(horizon_color);
	float radius=(zmax+zmin)*0.5;
	render_util::render_sphere(radius, n_horizon_parallels, n_horizon_parpoints, n_horizon_meridians, n_horizon_merpoints);
	glPopAttrib();

	glEndList();
    }
    else
    {
	glCallList(horizon_call_list);
    }

    glPopMatrix();
}

void render_obj_camera::set_camera_parameters(float fov_, float zmin_, float zmax_, float aspect_)
{
    // save parameters
    fov=fov_;
    zmin=zmin_;
    zmax=zmax_;
    aspect=aspect_;

    // this matrix is the same that the one from gluPerspective()
    double m[IS_MAT4X4];
    double prec=1.0/(zmin-zmax);
    focal=1.0/tan(M_PI/180.0*fov*0.5);
    m[1]=m[2]=m[3]=m[4]=m[6]=m[7]=m[8]=m[9]=m[12]=m[13]=m[15]=0;
    m[0]=focal/aspect;
    m[5]=focal;
    m[10]=(double)(zmax+zmin)*prec;
    m[11]=-1;
    m[14]=2.0*(double)zmax*zmin*prec;

    // set object projection
    set_projection(m);
}



// TESTS
#if 0
    if(ctx->get_pass()==RENDER_PASS_SELECT || ctx->get_pass()==RENDER_PASS_OPAQUE)
    {
	matrix4x4 model, proj, inv;
	double tmp[16];
	model.declared_local_or_member();
	proj.declared_local_or_member();
	inv.declared_local_or_member();
	glGetDoublev(GL_MODELVIEW_MATRIX, model.get_coefs());
	glGetDoublev(GL_PROJECTION_MATRIX, proj.get_coefs());
	ERROR("--------------------------------------------------------------------------------");
	proj.dump_stdout();
	proj.matrix_mult(model.get_coefs(), tmp);
	proj.set_coefs(tmp);
	proj.invert(&inv);
	glGetDoublev(GL_PROJECTION_MATRIX, proj.get_coefs());

	struct
	{
	    double vertex[4];
	} vlist[]=
	  {
#define Z 70
	      {{-0.5, -0.5, Z, 1}},
	      {{ 0.5, -0.5, Z, 1}},
	      {{ 0.5,  0.5, Z, 1}},
	      {{-0.5,  0.5, Z, 1}},

	      {{-0.5,  0.5, Z, 1}},
	      {{ 0.5,  0.5, Z, 1}},
	      {{ 0.5, -0.5, Z, 1}},
	      {{-0.5, -0.5, Z, 1}},
#undef Z

	      {{NAN, NAN, NAN, NAN}},
	  };

	glColor3f(0, 1, 0);
	glDepthFunc(GL_ALWAYS); // clear depth buffer
	glBegin(GL_QUADS);
	for(int i=0; !isnan(vlist[i].vertex[0]); i++)
	{
	    // to projection
	    double xyzw_projection[4];
	    xyzw_projection[0]=vlist[i].vertex[0]*vlist[i].vertex[2];
	    xyzw_projection[1]=vlist[i].vertex[1]*vlist[i].vertex[2];
	    xyzw_projection[2]=-vlist[i].vertex[2]*proj.get_coef(2, 2)+proj.get_coef(2, 3);
	    xyzw_projection[3]=vlist[i].vertex[2];
	    //ERROR("XYZ %d %9.3f %9.3f %9.3f %9.3f", i, xyzw_projection[0], xyzw_projection[1], xyzw_projection[2], xyzw_projection[3]);
	    //proj.dump_stdout();
	    //ERROR("--------------------------------------------------------------------------------");

 #if 0
	    double res[4];
	    inv.vect_mult(xyzw_projection, res);
	    ERROR("RES %d %9.3f %9.3f %9.3f %9.3f", i, res[0], res[1], res[2], res[3]);
	    glVertex4dv(res);

	    model.vect_mult(res, tmp);
	    ERROR("MOD %d %9.3f %9.3f %9.3f %9.3f", i, tmp[0], tmp[1], tmp[2], tmp[3]);

	    proj.vect_mult(tmp, res);
	    ERROR("PRJ %d %9.3f %9.3f %9.3f %9.3f", i, res[0], res[1], res[2], res[3]);

	    res[0]/=res[3];
	    res[1]/=res[3];
	    res[2]/=res[3];
	    ERROR("LST %d %9.3f %9.3f %9.3f %9.3f", i, res[0], res[1], res[2], res[3]);
#endif
	}
	glEnd();
	glDepthFunc(GL_LESS);
    }
#endif
